diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-26 18:09:18 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-26 18:09:18 +0900 |
| commit | 8547034e6d82e4d1184f35af2dbff67180d89dc8 (patch) | |
| tree | 2e1835040f39adc7d0c410a108ebb558f9971a8b /app/[lng] | |
| parent | 3131dce1f0c90d960f53bd384045b41023064bc4 (diff) | |
(김준회) dolce: 동기화 기능 추가, 로컬 다운로드, 삭제 추가, 동기화 dialog 개선 등
Diffstat (limited to 'app/[lng]')
| -rw-r--r-- | app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx | 585 | ||||
| -rw-r--r-- | app/[lng]/partners/(partners)/dolce-upload-v3/page.tsx | 49 |
2 files changed, 634 insertions, 0 deletions
diff --git a/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx b/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx new file mode 100644 index 00000000..513cfe1e --- /dev/null +++ b/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx @@ -0,0 +1,585 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import { useState, useEffect, useCallback, useMemo } from "react"; +import { useParams } from "next/navigation"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { InfoIcon, RefreshCw, Search, Upload, Plus, Cloud } from "lucide-react"; +import { toast } from "sonner"; +import { useTranslation } from "@/i18n/client"; +import { + UnifiedDwgReceiptItem, + DetailDwgReceiptItem, + FileInfoItem, + fetchDwgReceiptList, + getVendorSessionInfo, + fetchVendorProjects, + fetchDetailDwgReceiptListV2, + fetchFileInfoListV2, + fetchPendingSyncItems, + deleteLocalFile, +} from "@/lib/dolce-v2/actions"; +import { DrawingListTableV2 } from "@/lib/dolce/table/drawing-list-table-v2"; +import { drawingListColumns } from "@/lib/dolce/table/drawing-list-columns"; +import { createGttDrawingListColumns, DocumentType } from "@/lib/dolce/table/gtt-drawing-list-columns"; +import { createDetailDrawingColumns } from "@/lib/dolce/table/detail-drawing-columns"; +import { createFileListColumns } from "@/lib/dolce/table/file-list-columns"; + +// 다이얼로그 (V2/V3) +import { B4BulkUploadDialogV3Sync } from "@/lib/dolce-v2/dialogs/b4-bulk-upload-dialog-v3"; +import { AddAndModifyDetailDrawingDialogV2 } from "@/lib/dolce-v2/dialogs/add-and-modify-detail-drawing-dialog-v2"; +import { UploadFilesToDetailDialogV2 } from "@/lib/dolce-v2/dialogs/upload-files-to-detail-dialog-v2"; +import { SyncItemsDialog } from "@/lib/dolce-v2/dialogs/sync-items-dialog"; + +interface DolceUploadPageV3Props { + searchParams: { [key: string]: string | string[] | undefined }; +} + +export default function DolceUploadPageV3({ searchParams }: DolceUploadPageV3Props) { + const params = useParams(); + const lng = params?.lng as string; + const { t } = useTranslation(lng, "dolce"); + + // URL에서 초기 프로젝트 코드 + const initialProjNo = (searchParams.projNo as string) || ""; + + // 상태 관리 + const [drawings, setDrawings] = useState<UnifiedDwgReceiptItem[]>([]); + const [projects, setProjects] = useState<Array<{ code: string; name: string }>>([]); + const [vendorInfo, setVendorInfo] = useState<{ + userId: string; + userName: string; + email: string; + vendorCode: string; + vendorName: string; + drawingKind: "B3" | "B4"; + } | null>(null); + const [isLoading, setIsLoading] = useState(true); + const [isRefreshing, setIsRefreshing] = useState(false); + const [error, setError] = useState<string | null>(null); + + // 필터 상태 + const [projNo, setProjNo] = useState(initialProjNo); + const [drawingNo, setDrawingNo] = useState(""); + const [drawingName, setDrawingName] = useState(""); + const [discipline, setDiscipline] = useState(""); + const [manager, setManager] = useState(""); + const [documentType, setDocumentType] = useState<DocumentType>("ALL"); // B4 전용 + + // 선택된 도면 및 상세도면 + const [selectedDrawing, setSelectedDrawing] = useState<UnifiedDwgReceiptItem | null>(null); + const [detailDrawings, setDetailDrawings] = useState<DetailDwgReceiptItem[]>([]); + const [selectedDetail, setSelectedDetail] = useState<DetailDwgReceiptItem | null>(null); + const [files, setFiles] = useState<FileInfoItem[]>([]); + const [isLoadingDetails, setIsLoadingDetails] = useState(false); + const [, setIsLoadingFiles] = useState(false); + + // 다이얼로그 상태 + const [bulkUploadDialogOpen, setBulkUploadDialogOpen] = useState(false); + const [addDialogOpen, setAddDialogOpen] = useState(false); + const [uploadFilesDialogOpen, setUploadFilesDialogOpen] = useState(false); + const [syncDialogOpen, setSyncDialogOpen] = useState(false); + + // 동기화 상태 + const [pendingSyncCount, setPendingSyncCount] = useState(0); + + // 미동기화 건수 확인 + const checkPendingSync = useCallback(async () => { + if (!projNo || !vendorInfo) return; + try { + const items = await fetchPendingSyncItems({ projectNo: projNo, userId: vendorInfo.userId }); + setPendingSyncCount(items.length); + } catch (e) { + console.error("Failed to check pending sync items", e); + } + }, [projNo, vendorInfo]); + + // 초기 데이터 로드 + const loadInitialData = useCallback(async () => { + try { + setIsLoading(true); + setError(null); + + const [vendorInfoData, projectsData] = await Promise.all([ + getVendorSessionInfo(), + fetchVendorProjects(), + ]); + + setVendorInfo(vendorInfoData as typeof vendorInfo); + setProjects(projectsData); + + if (initialProjNo) { + const drawingsData = await fetchDwgReceiptList({ + project: initialProjNo, + drawingKind: vendorInfoData.drawingKind, + drawingVendor: vendorInfoData.drawingKind === "B3" ? vendorInfoData.vendorCode : "", + }); + setDrawings(drawingsData); + } + } catch (err) { + console.error("초기 데이터 로드 실패:", err); + setError(err instanceof Error ? err.message : t("page.initialLoadError")); + toast.error(t("page.initialLoadError")); + } finally { + setIsLoading(false); + } + }, [initialProjNo, t]); + + // 도면 목록 조회 + const loadDrawings = useCallback(async () => { + if (!projNo || !vendorInfo) return; + + try { + setIsRefreshing(true); + setError(null); + + const drawingsData = await fetchDwgReceiptList({ + project: projNo, + drawingKind: vendorInfo.drawingKind, + drawingVendor: vendorInfo.drawingKind === "B3" ? vendorInfo.vendorCode : "", + }); + + setDrawings(drawingsData); + toast.success(t("page.drawingLoadSuccess")); + + // 동기화 상태 체크 + checkPendingSync(); + + } catch (err) { + console.error("도면 로드 실패:", err); + setError(err instanceof Error ? err.message : t("page.drawingLoadError")); + toast.error(t("page.drawingLoadError")); + } finally { + setIsRefreshing(false); + } + }, [projNo, vendorInfo, t, checkPendingSync]); + + // 상세도면 목록 로드 (V2 API 사용) + const loadDetailDrawings = useCallback(async () => { + if (!selectedDrawing) { + setDetailDrawings([]); + setSelectedDetail(null); + return; + } + + try { + setIsLoadingDetails(true); + // V2: 로컬 임시 저장 건 포함 조회 + const data = await fetchDetailDwgReceiptListV2({ + project: selectedDrawing.ProjectNo, + drawingNo: selectedDrawing.DrawingNo, + discipline: selectedDrawing.Discipline, + drawingKind: selectedDrawing.DrawingKind, + userId: vendorInfo?.userId || "", + }); + setDetailDrawings(data); + + if (data.length > 0) { + setSelectedDetail(data[0]); + } else { + setSelectedDetail(null); + } + + // 동기화 상태 체크 + checkPendingSync(); + + } catch (error) { + console.error("상세도면 로드 실패:", error); + toast.error(t("detailDialog.detailLoadError")); + setDetailDrawings([]); + setSelectedDetail(null); + } finally { + setIsLoadingDetails(false); + } + }, [selectedDrawing, vendorInfo, t, checkPendingSync]); + + // 파일 목록 로드 (V2 API 사용) + const loadFiles = useCallback(async () => { + if (!selectedDetail) { + setFiles([]); + return; + } + + try { + setIsLoadingFiles(true); + // V2: 로컬 임시 파일 포함 조회 + const data = await fetchFileInfoListV2(selectedDetail.UploadId); + setFiles(data); + + // 동기화 상태 체크 + checkPendingSync(); + + } catch (error) { + console.error("파일 목록 로드 실패:", error); + toast.error(t("detailDialog.fileLoadError")); + setFiles([]); + } finally { + setIsLoadingFiles(false); + } + }, [selectedDetail, t, checkPendingSync]); + + // 동기화 완료 핸들러 + const handleSyncComplete = useCallback(() => { + checkPendingSync(); + if (selectedDrawing) loadDetailDrawings(); + if (selectedDetail) loadFiles(); + }, [checkPendingSync, selectedDrawing, selectedDetail, loadDetailDrawings, loadFiles]); + + // 초기 데이터 로드 + useEffect(() => { + loadInitialData(); + }, [loadInitialData]); + + // 프로젝트 변경 시 자동 검색 + useEffect(() => { + if (projNo && vendorInfo) { + loadDrawings(); + } + }, [projNo, vendorInfo, loadDrawings]); + + // 선택된 도면 변경 시 상세도면 로드 + useEffect(() => { + loadDetailDrawings(); + }, [selectedDrawing, loadDetailDrawings]); + + // 선택된 상세도면 변경 시 파일 목록 로드 + useEffect(() => { + loadFiles(); + }, [selectedDetail, loadFiles]); + + const handleDrawingClick = (drawing: UnifiedDwgReceiptItem) => { + setSelectedDrawing(drawing); + }; + + const handleSearch = () => { + loadDrawings(); + }; + + const handleRefresh = () => { + loadDrawings(); + }; + + const handleRefreshDetails = () => { + loadDetailDrawings(); + }; + + // 완료 핸들러들 + const handleBulkUploadComplete = () => { + loadDrawings(); + checkPendingSync(); + }; + const handleAddComplete = () => { + setAddDialogOpen(false); + loadDetailDrawings(); + checkPendingSync(); + }; + const handleUploadComplete = () => { + setUploadFilesDialogOpen(false); + loadFiles(); + checkPendingSync(); + }; + + const handleDeleteFile = async (file: FileInfoItem) => { + if (!confirm(lng === "ko" ? "정말로 파일을 삭제하시겠습니까?" : "Are you sure you want to delete this file?")) return; + + try { + const result = await deleteLocalFile(file.FileId); + if (result.success) { + toast.success(lng === "ko" ? "파일이 삭제되었습니다." : "File deleted."); + loadFiles(); + checkPendingSync(); + } else { + throw new Error(result.error); + } + } catch (e) { + console.error("File delete failed", e); + toast.error(lng === "ko" ? "파일 삭제 실패" : "File delete failed"); + } + }; + + const handleDownload = async (file: FileInfoItem) => { + try { + toast.info(t("detailDialog.downloadPreparing")); + const response = await fetch("/api/dolce/download", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + fileId: file.FileId, + userId: file.CreateUserId, + fileName: file.FileName, + }), + }); + + if (!response.ok) throw new Error(t("detailDialog.downloadError")); + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = file.FileName; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success(t("detailDialog.downloadSuccess")); + } catch (error) { + console.error("파일 다운로드 실패:", error); + toast.error(t("detailDialog.downloadError")); + } + }; + + // 필터 로직 + const filteredDrawings = useMemo(() => { + let result = drawings.filter((drawing) => { + if (drawingNo && !drawing.DrawingNo.toLowerCase().includes(drawingNo.toLowerCase())) return false; + if (drawingName && !drawing.DrawingName.toLowerCase().includes(drawingName.toLowerCase())) return false; + if (discipline && !drawing.Discipline?.toLowerCase().includes(discipline.toLowerCase())) return false; + if (manager && !drawing.Manager.toLowerCase().includes(manager.toLowerCase()) && + !drawing.ManagerENM?.toLowerCase().includes(manager.toLowerCase())) return false; + return true; + }); + + if (vendorInfo?.drawingKind === "B4" && documentType !== "ALL") { + result = result.filter((drawing) => { + if (drawing.DrawingKind !== "B4") return false; + const gttDrawing = drawing as { DrawingMoveGbn?: string }; + if (documentType === "SHI_INPUT") return gttDrawing.DrawingMoveGbn === "도면제출"; + else if (documentType === "GTT_DELIVERABLES") return gttDrawing.DrawingMoveGbn === "도면입수"; + return true; + }); + } + return result; + }, [drawings, drawingNo, drawingName, discipline, manager, vendorInfo?.drawingKind, documentType]); + + const getDetailDrawingId = (detail: DetailDwgReceiptItem) => `${detail.RegisterId}_${detail.UploadId}`; + const getDrawingId = (drawing: UnifiedDwgReceiptItem) => `${drawing.ProjectNo}_${drawing.DrawingNo}_${drawing.Discipline}`; + + const canAddDetailDrawing = vendorInfo && ( + vendorInfo.drawingKind === "B3" || + (vendorInfo.drawingKind === "B4" && selectedDrawing && 'DrawingMoveGbn' in selectedDrawing && selectedDrawing.DrawingMoveGbn === "도면입수") + ); + + // 파일 리스트 컬럼 정의 + const fileColumns = createFileListColumns({ + onDownload: handleDownload, + onDelete: handleDeleteFile, + lng + }); + + if (isLoading) { + return ( + <div className="space-y-4"> + <Card><CardHeader><Skeleton className="h-8 w-48" /></CardHeader><CardContent><Skeleton className="h-32 w-full" /></CardContent></Card> + <Card><CardHeader><Skeleton className="h-8 w-48" /></CardHeader><CardContent><Skeleton className="h-96 w-full" /></CardContent></Card> + </div> + ); + } + + return ( + <div className="space-y-4 max-w-full overflow-x-hidden h-full flex flex-col"> + {error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>} + + {/* 헤더 및 Sync 컨트롤 */} + <div className="flex items-center justify-between"> + <div> + <h2 className="text-xl font-bold flex items-center gap-2"> + Dolce Upload V3 <span className="text-xs font-normal bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 px-2 py-1 rounded-full">Sync Enabled</span> + </h2> + </div> + <div className="flex items-center gap-2"> + <Button + size="sm" + variant={pendingSyncCount > 0 ? "default" : "outline"} + onClick={() => setSyncDialogOpen(true)} // 다이얼로그 열기 + disabled={pendingSyncCount === 0 || !projNo} + > + <Cloud className="h-4 w-4 mr-2" /> + Send to SHI + </Button> + </div> + </div> + + {!projNo && <Alert><InfoIcon className="h-4 w-4" /><AlertDescription>{t("page.selectProject")}</AlertDescription></Alert>} + + {/* 필터 카드 */} + <Card className="flex-shrink-0"> + <CardHeader className="py-3"><CardTitle className="text-base">{t("filter.title")}</CardTitle></CardHeader> + <CardContent className="py-3"> + <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-3"> + <div className="space-y-1"> + <Label className="text-xs">{t("filter.project")}</Label> + <Select value={projNo} onValueChange={setProjNo}> + <SelectTrigger className="h-8"><SelectValue placeholder={t("filter.projectPlaceholder")} /></SelectTrigger> + <SelectContent> + {projects.map((p) => <SelectItem key={p.code} value={p.code}>{p.code} - {p.name}</SelectItem>)} + </SelectContent> + </Select> + </div> + {/* 기타 필터들 */} + <div className="space-y-1"><Label className="text-xs">{t("filter.drawingNo")}</Label><Input className="h-8" value={drawingNo} onChange={(e) => setDrawingNo(e.target.value)} /></div> + <div className="space-y-1"><Label className="text-xs">{t("filter.drawingName")}</Label><Input className="h-8" value={drawingName} onChange={(e) => setDrawingName(e.target.value)} /></div> + <div className="space-y-1"><Label className="text-xs">{t("filter.discipline")}</Label><Input className="h-8" value={discipline} onChange={(e) => setDiscipline(e.target.value)} /></div> + <div className="space-y-1"><Label className="text-xs">{t("filter.manager")}</Label><Input className="h-8" value={manager} onChange={(e) => setManager(e.target.value)} /></div> + {vendorInfo?.drawingKind === "B4" && ( + <div className="space-y-1"> + <Label className="text-xs">{t("filter.documentType")}</Label> + <Select value={documentType} onValueChange={(v) => setDocumentType(v as DocumentType)}> + <SelectTrigger className="h-8"><SelectValue /></SelectTrigger> + <SelectContent> + <SelectItem value="ALL">{t("filter.documentTypeAll")}</SelectItem> + <SelectItem value="GTT_DELIVERABLES">{t("filter.documentTypeGttDeliverables")}</SelectItem> + <SelectItem value="SHI_INPUT">{t("filter.documentTypeSHIInput")}</SelectItem> + </SelectContent> + </Select> + </div> + )} + </div> + <div className="flex gap-2 mt-3 justify-end"> + <Button size="sm" onClick={handleSearch} disabled={!projNo || isRefreshing}><Search className="h-4 w-4 mr-2" />{t("filter.searchButton")}</Button> + {vendorInfo?.drawingKind === "B4" && ( + <Button size="sm" onClick={() => setBulkUploadDialogOpen(true)} disabled={!projNo || isRefreshing}> + <Upload className="h-4 w-4 mr-2" />{t("filter.bulkUploadButton")} + </Button> + )} + <Button size="sm" variant="outline" onClick={handleRefresh} disabled={!projNo || isRefreshing}><RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} /></Button> + </div> + </CardContent> + </Card> + + {/* 메인 컨텐츠 영역 */} + <Card className="flex-shrink-0" style={{ minHeight: "400px" }}> + <CardHeader className="py-3"><CardTitle className="text-base">{t("drawingList.title")}</CardTitle></CardHeader> + <CardContent className="p-0"> + <DrawingListTableV2 + columns={ + vendorInfo?.drawingKind === "B4" + ? (createGttDrawingListColumns({ documentType, lng, t }) as unknown as typeof drawingListColumns as any) + : (drawingListColumns(lng, t) as unknown as typeof drawingListColumns as any) + } + data={filteredDrawings} + onRowClick={handleDrawingClick} + selectedRow={selectedDrawing || undefined} + getRowId={getDrawingId} + maxHeight="400px" + minHeight="300px" + /> + </CardContent> + </Card> + + <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 flex-1 min-h-0"> + <Card className="flex flex-col min-h-0"> + <CardHeader className="flex-row items-center justify-between py-3"> + <CardTitle className="text-base">{t("detailDialog.detailListTitle")}</CardTitle> + <div className="flex gap-2"> + <Button variant="outline" size="sm" onClick={handleRefreshDetails} disabled={!selectedDrawing}><RefreshCw className={`h-4 w-4 ${isLoadingDetails ? "animate-spin" : ""}`} /></Button> + {canAddDetailDrawing && <Button size="sm" onClick={() => setAddDialogOpen(true)} disabled={!selectedDrawing}><Plus className="h-4 w-4 mr-2" />{t("detailDialog.addDetailButton")}</Button>} + </div> + </CardHeader> + <CardContent className="p-0 flex-1"> + <DrawingListTableV2 + columns={createDetailDrawingColumns(lng, t) as any} + data={detailDrawings} + onRowClick={setSelectedDetail as any} + selectedRow={selectedDetail || undefined} + getRowId={getDetailDrawingId} + maxHeight="400px" + minHeight="300px" + /> + </CardContent> + </Card> + + <Card className="flex flex-col min-h-0"> + <CardHeader className="flex-row items-center justify-between py-3"> + <CardTitle className="text-base">{t("detailDialog.fileListTitle")}</CardTitle> + {selectedDetail && canAddDetailDrawing && ( + <Button size="sm" onClick={() => setUploadFilesDialogOpen(true)}><Upload className="h-4 w-4 mr-2" />{t("detailDialog.uploadFilesButton")}</Button> + )} + </CardHeader> + <CardContent className="p-0 flex-1"> + <DrawingListTableV2 + columns={fileColumns as any} + data={files} + maxHeight="400px" + minHeight="300px" + /> + </CardContent> + </Card> + </div> + + {/* 다이얼로그 영역 */} + {vendorInfo && vendorInfo.drawingKind === "B4" && projNo && ( + <B4BulkUploadDialogV3Sync + open={bulkUploadDialogOpen} + onOpenChange={setBulkUploadDialogOpen} + projectNo={projNo} + userId={vendorInfo.userId} + userName={vendorInfo.userName} + userEmail={vendorInfo.email} + vendorCode={vendorInfo.vendorCode} + onUploadComplete={handleBulkUploadComplete} + lng={lng} + /> + )} + + {vendorInfo && selectedDrawing && ( + <AddAndModifyDetailDrawingDialogV2 + open={addDialogOpen} + onOpenChange={setAddDialogOpen} + drawing={selectedDrawing} + vendorCode={vendorInfo.vendorCode} + userId={vendorInfo.userId} + userName={vendorInfo.userName} + userEmail={vendorInfo.email} + onComplete={handleAddComplete} + drawingKind={vendorInfo.drawingKind} + lng={lng} + /> + )} + + {vendorInfo && selectedDetail && ( + <UploadFilesToDetailDialogV2 + open={uploadFilesDialogOpen} + onOpenChange={setUploadFilesDialogOpen} + uploadId={selectedDetail.UploadId} + drawingNo={selectedDetail.DrawingNo} + revNo={selectedDetail.DrawingRevNo} + // 추가된 props + drawingName={selectedDrawing?.DrawingName} + discipline={selectedDrawing?.Discipline} + registerKind={selectedDetail.RegisterKind} + + userId={vendorInfo.userId} + projectNo={projNo} + vendorCode={vendorInfo.vendorCode} // 추가: Vendor Code + onUploadComplete={handleUploadComplete} + lng={lng} + /> + )} + + {/* 동기화 다이얼로그 */} + {vendorInfo && projNo && ( + <SyncItemsDialog + open={syncDialogOpen} + onOpenChange={setSyncDialogOpen} + projectNo={projNo} + userId={vendorInfo.userId} + vendorCode={vendorInfo.vendorCode} + onSyncComplete={handleSyncComplete} + lng={lng} + /> + )} + </div> + ); +} diff --git a/app/[lng]/partners/(partners)/dolce-upload-v3/page.tsx b/app/[lng]/partners/(partners)/dolce-upload-v3/page.tsx new file mode 100644 index 00000000..f62f486b --- /dev/null +++ b/app/[lng]/partners/(partners)/dolce-upload-v3/page.tsx @@ -0,0 +1,49 @@ +import { Suspense } from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import DolceUploadPageV3 from "./dolce-upload-page-v3"; +import { Shell } from "@/components/shell"; + +export const metadata = { + title: "조선 벤더문서 업로드(DOLCE) V3", + description: "조선 설계문서 업로드 및 관리 - 오프라인 동기화 지원", +}; + +function DolceUploadSkeleton() { + return ( + <div className="space-y-4"> + <Card><CardHeader><Skeleton className="h-8 w-48" /></CardHeader><CardContent><Skeleton className="h-32 w-full" /></CardContent></Card> + <Card><CardHeader><Skeleton className="h-8 w-48" /></CardHeader><CardContent><Skeleton className="h-96 w-full" /></CardContent></Card> + </div> + ); +} + +export default async function DolceUploadPageWrapper({ + params, + searchParams, +}: { + params: Promise<{ lng: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}) { + const { lng } = await params; + const resolvedParams = await searchParams; + + return ( + <Shell variant="fullscreen"> + <div className="flex items-center justify-between flex-shrink-0"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + {lng === "ko" ? "DOLCE 도면 업로드 V3 (동기화)" : "DOLCE Drawing Upload V3 (Sync)"} + </h2> + <p className="text-muted-foreground"> + {lng === "ko" ? "임시 저장 및 서버 동기화 기능을 지원합니다." : "Supports temporary save and server synchronization."} + </p> + </div> + </div> + + <Suspense fallback={<DolceUploadSkeleton />}> + <DolceUploadPageV3 searchParams={resolvedParams} /> + </Suspense> + </Shell> + ); +} |
